#include <iostream>
#include <map>
#include <string>
#include <vector>
#include <sstream>

#include <pthread.h>
#include <signal.h>

#include "Disk.h"
#include "FileSystem.h"
#include "System.h"
#include "Process.h"
#include "tstream.h"

#define max(a,b) ((a) > (b) ? (a) : (b))
#define min(a,b) ((a) > (b) ? (b) : (a))

using namespace std;

typedef int (*Func)(vector<string>);


map <string,Func> menu;
map<int, Process*> processes;
int active_proc;

FileSystem* fs;
System* sys;
Disk* disk;


//utility functions **************************************************************************************************


void terminate(int exitCode) {
	cout << endl << "Shutting down system." << endl;
	for(map<int, Process*>::iterator it = processes.begin(); it != processes.end(); it++) {
		delete it->second;
	}
	delete sys;
	delete fs;
	delete disk;
	exit(exitCode);
}


int full(vector<string> args) {
	cout << "__ Full tree data, start from root '/' __:"<<endl;
	sys->full(0,0);
	cout << "___________________________________"<<endl;
	return 0;
}


// dispatcher functions *******************************************************************************************

int exit(vector<string> args) {
	terminate(0);
	return 0;
}

int isNumber(string str) {
	for(unsigned int i = 0; i < str.length(); i++)
		if (!isdigit(str[i]))
			return false;
	return true;
}

int str2int (const string &str) {
	stringstream ss(str);
	int n;
	ss >> n;
	return n;
}

int swprc(vector<string> args) {
	if (args.size() == 1) {
		if (isNumber(args[0])) {
			int p = str2int(args[0]);
			if(processes[p]!=NULL) {
				active_proc = p;
				return 0;
			}
			else {
				cerr <<"error: no process exists with id " << p << endl;
				return -1;
			}
		}
		cerr <<"illegal pid: given " << args[0] << endl;
		return -1;
	}
	cerr << "correct syntax is: swprc <pid>  -  switch to proc with id pid" << endl;
	return -1;
}

int crprc(vector<string> args) {
	int id, parent;
	if (args.size() == 1 || args.size() == 2) {
		if (!isNumber(args[0])) {
			cerr << "correct syntax is: crprc <id> <parent>" << endl;
			return -1;
		}
		id = str2int(args[0]);
		if (args.size() == 1) {
			parent=-1;
		}
		else if (!isNumber(args[1].c_str()) & str2int(args[1]) != -1) {
			cerr << "correct syntax is: crprc <id> <parent>" << endl;
			return -1;
		}
		else {
			parent=str2int(args[1]);
		}
	}
	else {
		cerr << "correct syntax is: crprc <id> <parent>" << endl;
		return -1;
	}


	if(processes[id]!=NULL)  {
		cerr <<"error: process already exists with id " << id << endl;
		return -1;
	}
	if(parent==-1) {
		processes[id] = new Process(sys);
	}
	else if(processes[parent]!=NULL) {
		processes[id] = new Process(sys, processes[parent]);
	}
	else {
		cerr<<"error: parent process "<<parent<<" doesn't exists!"<<endl;
		return -1;
	}
	return 0;
}

int status(vector<string> args) {
	for(map<int, Process*>::iterator it = processes.begin(); it != processes.end(); it++){
		cout << "Process "<< it->first <<": ";
		it->second->status();
	}
	fs->status();
	sys->status();
	disk->status();
}

int clear(vector<string> args) {
	return system("clear");
}

void runCmd(vector<string> args, bool wait) {
	Func f = menu[args[0]];
	if (f != NULL) {
		args.erase(args.begin());
		int ret = f(args);
		cout << ret <<endl;
	}
	else {
		processes[active_proc]->enqueCmd(args);
		if(wait) {
			do{
				pthread_yield();
			} while(processes[active_proc]->isBusy());
		}
	}
}

int batch(const char* filename, bool isScript) {
	FILE* stream;
	if ((stream = fopen(filename, "r")) == 0) {
		cerr << "batch: Cannot open batch file: " << filename << endl;
		return -1;
	}
	char cmd[1024];
	string space(" ");
	while (!feof(stream)) {
		cmd[0] = '\0';
		fgets(cmd,1024,stream);
		if (cmd[0] != '#' && cmd[0] != '\n' && cmd[0] != '\r' && cmd[0] != '\0') {
			if(cmd[strlen(cmd)-1] =='\n')
				cmd[strlen(cmd)-1] ='\0';
			//cout << "*" << cmd << "*" << endl;
//			char* tok;
//			vector<const char*> args;
//			tok = strtok (cmd," ");
//			while (tok != NULL) {
//				args.push_back(tok);
//				tok = strtok (NULL, " ");
//			}
			vector<string> args;

			/////////
			string str(cmd);
			for(string::size_type lastPos = str.find_first_not_of(space, 0),
				pos     = str.find_first_of(space, lastPos);
				string::npos != pos || string::npos != lastPos;
				lastPos = str.find_first_not_of(space, pos),
		        pos = str.find_first_of(space, lastPos))   {
					//string tok=str.substr(lastPos, pos - lastPos);
				args.push_back(str.substr(lastPos, pos - lastPos));
			}

			runCmd(args, isScript);
		}
	}
	fclose(stream);
	return 0;
}

int runBatch(vector<string> args) {
	if (args.size() == 1)
		return batch(args[0].c_str(),false);
	cerr << "correct syntax is: batch <filename>" << endl;
	return -1;
}

int runScript(vector<string> args) {
	if (args.size() == 1)
		return batch(args[0].c_str(),true);
	cerr << "correct syntax is: script <filename>" << endl;
	return -1;
}
// blowWare functions *************************************************************************************************

//return true if str2 is a prefix of str1
bool isPrefix(const char* str1,const char* str2) {
	return (strspn(str1,str2) == strlen(str2));
}

//filters fileList according to token prefix.
// if filterType = 1, returns only directories. filterType = 2 returns files, filterType = 0 returns both.
int filterFileList(char* fileList,const char* token,int filterType) {
	string newList = "";
	bool ignore = (filterType == 0);
	bool isDir = (filterType == 1);
	int count = 0;
	char* tok;
	tok = strtok (fileList," ");
	while (tok != NULL) {
		if (isPrefix(tok,token) && (ignore | !(isDir ^ tok[strlen(tok)-1] == '/'))) {
			newList += tok;
			if(!newList.empty())
				newList += " ";
			if (count%15 == 14)
				newList += "\n";
			++count;
		}
		tok = strtok (NULL, " ");
	}
	strcpy(fileList,newList.c_str());
	return count;
}

void shell() {
	vector<string> history;
	int historyIndex = -1;
	int cmdType = 42;
	char cmd[1024]; //getLine buffer
	char fileList[40960]; //TAB support
	cmd[0] = '\0';
	string space(" ");
	do {
		if (cmdType == 0 && strlen(cmd) > 0) {
			//char tmp[1024];
			//strcpy(tmp,cmd);
			vector<string> args;


			/////////
			string str(cmd);
			for(string::size_type lastPos = str.find_first_not_of(space, 0),
				pos     = str.find_first_of(space, lastPos);
				string::npos != pos || string::npos != lastPos;
				lastPos = str.find_first_not_of(space, pos),
		        pos = str.find_first_of(space, lastPos))   {
					//string tok=str.substr(lastPos, pos - lastPos);
				args.push_back(str.substr(lastPos, pos - lastPos));
			}
			/////////////////



//			char *tok = strtok (tmp," ");
//			while (tok != NULL) {
//				args.push_back(tok);
//				tok = strtok (NULL, " ");
//			}

			runCmd(args, true);

			if (historyIndex + 1 == history.size())
				history.push_back(string(cmd));
			else
				history[historyIndex + 1] = string(cmd);
			++historyIndex;
			cmd[0] = '\0'; //clear current buffer.
		}
		else if (cmdType == 1) { //TAB
			char* ptr = strrchr(cmd,' ') == NULL ? cmd : strrchr(cmd,' ')+1; //points to the last token
			string prefix;                //whats before the slash (inc.)
			string suffix(ptr);           //whats after the slash (exc.)
			int slash = -1;
			if ((slash = suffix.find_last_of('/')) != -1) {
				prefix = suffix.substr(0,min(slash+1,suffix.length()));
				suffix = suffix.substr(min(slash+1,suffix.length()),suffix.length());
			}
			string path = prefix + string(processes[active_proc]->getCurrentWorkingDirectoryForTomer());
			if (processes[active_proc]->ls(path.c_str(),fileList) == 0) {
				int filterType = 0;
				if (isPrefix(cmd,"cd ") || isPrefix(cmd,"ls ") || isPrefix(cmd,"rmdir ") || isPrefix(cmd,"rmdir_r "))
					filterType = 1;
				if (isPrefix(cmd,"hdlink ") || isPrefix(cmd,"sflink ") || isPrefix(cmd,"writeFile ") ||
						isPrefix(cmd,"open ") || isPrefix(cmd,"rm ") || isPrefix(cmd,"mv "))
					filterType = 2;
				if (filterFileList(fileList,suffix.c_str(),filterType) == 1) { //one match
					strcpy(ptr,prefix.c_str());
					strcpy(ptr+prefix.length(),fileList);
					cmd[strlen(cmd)-1] = '\0';
				}
				else if (strlen(fileList) > 0)
					cout << fileList << endl;
			}
		}
		else if (cmdType == -2 && historyIndex >= 0) { //pressing up goes down the history list
			strcpy(cmd,history[historyIndex].c_str());
			historyIndex = max(historyIndex - 1,0);
		}
		else if (cmdType == -1 && historyIndex < history.size()) { //pressing down goes up the history list
			strcpy(cmd,history[historyIndex].c_str());
			historyIndex = min(historyIndex + 1,history.size()-1);
		}
		if (cmdType >= 0) {
			cout << active_proc << ": ";
			cout << processes[active_proc]->getCurrentWorkingDirectoryForTomer() << ">";
		}
	} while((cmdType = getLine(cmd)) != 666);
}

int main(int argc, char** argv) {
	if (argc == 2) {
		FILE* file = fopen (argv[1],"r");
		if(file==NULL) {
			cerr << "Shell::main: can't open configuration file: " << argv[1] << endl;
			exit(-1);
		}
		int blockSize = -1;
		int numInodes = -1;
		int diskSize = -1;
		while (!feof(file)) {
			char prop[20];
			int value;
			fscanf(file,"%s = %d",prop,&value);
			cout << "reading: " << prop << " = " << value << endl;
			if (strcmp(prop,"dataBlockSize") == 0)
				blockSize = value;
			else if (strcmp(prop,"numberOfInodes") == 0)
				numInodes = value;
			else if (strcmp(prop,"diskSize") == 0)
				diskSize = value;
			else {
				cerr << "Shell::main: invalid property value: "<< prop << endl;
				exit(-1);
			}
		}
		fclose(file);
		if (blockSize >=0 && numInodes >= 0 && diskSize >= 0)
			disk=new Disk(blockSize, numInodes, diskSize);
		else {
			cerr << "Shell::main: missing some properties. :(" << endl;
			exit(-1);
		}
	}
	else {
		FILE* file = fopen ("SYS_FILE","rb");
		if(file!=NULL) {
			fclose(file);
			disk = new Disk();
		}
		else {
			cerr << "Shell::main: SYS_FILE doesn't exist and configuration file found."<< endl;
			cerr << "Creating disk using default configuration." << endl;
			disk = new Disk(100,50,5000);
		}
	}

	fs = new FileSystem(disk);
	sys = new System(fs);

	initDispatcher();
	signal (SIGINT,terminate);

	menu["swprc"] = swprc;
	menu["sw"] = swprc;
	menu["crprc"] = crprc;
	menu["batch"] = runBatch;
	menu["script"] = runScript;
	menu["clear"] = clear;
	menu["f"] = full;
	menu["full"] = full;
	menu["status"] = status;
	menu["exit"] = exit;

	active_proc=0;
	processes[0] = new Process(sys);
	cerr.sync_with_stdio(true);
	shell();
	return 0;
}

